package club.itstudio.itouc.jw

import com.github.kittinunf.fuel.core.Parameters
import org.apache.commons.codec.digest.DigestUtils

import java.util.ArrayList
import java.util.Base64

/**
 * 教务系统参数加密工具
 */
object Encrypt {

    private val binaryArray = arrayOf("0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111")

    /**
     * 将字符串转化为des编码
     *
     * @param str 需要加密的String
     * @param key 密钥
     * @return des编码的String
     */
    private fun encodeDes(str: String, key: String?): String {
        val len = str.length
        val encData = StringBuilder()
        val keyBt: List<IntArray>
        val length: Int
        if (key != null && key != "") {
            keyBt = getKeyBytes(key)
            length = keyBt.size
            val iterator = len / 4
            val remainder = len % 4
            for (i in 0 until iterator) {
                val tempData = str.substring(i * 4, i * 4 + 4)
                val tempByte = strToBt(tempData)
                val encByte: IntArray
                var tempBt = tempByte
                for (x in 0 until length) {
                    tempBt = enc(tempBt, keyBt[x])
                }
                encByte = tempBt
                encData.append(bt64ToHex(encByte))
            }
            if (remainder > 0) {
                val remainderData = str.substring(iterator * 4, len)
                val tempByte = strToBt(remainderData)
                val encByte: IntArray
                var tempBt = tempByte
                for (x in 0 until length) {
                    tempBt = enc(tempBt, keyBt[x])
                }
                encByte = tempBt
                encData.append(bt64ToHex(encByte))
            }
        }
        return encData.toString()
    }

    /**
     * 将des编码的字符串进行解码
     *
     * @param str des编码的字符串
     * @param key 密钥
     * @return 解码后的字符串
     */
    private fun decodeDes(str: String, key: String?): String {
        val len = str.length
        val decStr = StringBuilder()
        val keyBt: List<IntArray>
        val length: Int
        if (key != null && key != "") {
            keyBt = getKeyBytes(key)
            length = keyBt.size
            val iterator = len / 16
            for (i in 0 until iterator) {
                val tempData = str.substring(i * 16, i * 16 + 16)
                val strByte = hexToBt64(tempData)
                val intByte = IntArray(64)
                for (j in 0..63) {
                    intByte[j] = Integer.parseInt(strByte.substring(j, j + 1))
                }
                val decByte: IntArray
                var tempBt = intByte
                for (x in length - 1 downTo 0) {
                    tempBt = dec(tempBt, keyBt[x])
                }
                decByte = tempBt
                decStr.append(byteToString(decByte))
            }
        }
        return decStr.toString()
    }

    //chang the string into the bit array
    //return bit array(it's length % 64 = 0)
    private fun getKeyBytes(key: String): List<IntArray> {
        val keyBytes = ArrayList<IntArray>()
        val len = key.length
        val iterator = len / 4
        val remainder = len % 4
        var i = 0
        while (i < iterator) {
            keyBytes.add(i, strToBt(key.substring(i * 4, i * 4 + 4)))
            i++
        }
        if (remainder > 0) {
            keyBytes.add(i, strToBt(key.substring(i * 4, len)))
        }
        return keyBytes
    }

    //chang the string(it's length <= 4) into the bit array
    //return bit array(it's length = 64)
    private fun strToBt(str: String): IntArray {
        val len = str.length
        val bt = IntArray(64)
        //计算str的前4个字符
        var i = 0
        while (i < len && i < 4) {
            val k = str[i].toInt()
            fullBit(bt, i, k)
            i++
        }
        // len小于4时补0计算
        for (p in len..3) {
            val k = 0
            fullBit(bt, p, k)
        }
        return bt
    }

    private fun fullBit(bt: IntArray, p: Int, k: Int) {
        for (q in 0..15) {
            var pow = 1
            for (m in 15 downTo q + 1) {
                pow *= 2
            }
            bt[16 * p + q] = k / pow % 2
        }
    }

    // 二进制转十六进制
    private fun bt4ToHex(binary: String): String {
        var hex = ""
        try {
            val i = Integer.parseInt(binary, 2)
            if (i > -1 && i < 16) {
                hex = Integer.toHexString(i)
            }
        } catch (ignored: Exception) {
        }

        return hex.toUpperCase()
    }

    // 十进制转二进制
    private fun getBoxBinary(i: Int): String {
        var binary = ""
        if (i > -1 && i < 16) {
            binary = binaryArray[i]
        }
        return binary
    }

    // 十六进制转二进制
    private fun hexToBt4(hex: String): String {
        var binary = ""
        try {
            val i = Integer.parseInt(hex, 16)
            if (i > -1 && i < 16) {
                binary = binaryArray[i]
            }
        } catch (ignored: Exception) {
        }

        return binary
    }


    private fun byteToString(byteData: IntArray): String {
        val str = StringBuilder()
        for (i in 0..3) {
            var count = 0
            for (j in 0..15) {
                var pow = 1
                for (m in 15 downTo j + 1) {
                    pow *= 2
                }
                count += byteData[16 * i + j] * pow
            }
            if (count != 0) {
                str.append(count.toChar())
            }
        }
        return str.toString()
    }

    private fun bt64ToHex(byteData: IntArray): String {
        val hex = StringBuilder()
        for (i in 0..15) {
            val bt = StringBuilder()
            for (j in 0..3) {
                bt.append(byteData[i * 4 + j])
            }
            hex.append(bt4ToHex(bt.toString()))
        }
        return hex.toString()
    }

    private fun hexToBt64(hex: String): String {
        val binary = StringBuilder()
        for (i in 0..15) {
            binary.append(hexToBt4(hex.substring(i, i + 1)))
        }
        return binary.toString()
    }

    private fun dec(dataByte: IntArray, keyByte: IntArray): IntArray {
        val index = IntArray(16)
        for (i in 0..15) {
            index[i] = 15 - i
        }
        return transferData(dataByte, keyByte, index)
    }

    private fun enc(dataByte: IntArray, keyByte: IntArray): IntArray {
        val index = IntArray(16)
        for (i in 0..15) {
            index[i] = i
        }
        return transferData(dataByte, keyByte, index)
    }

    private fun transferData(dataByte: IntArray, keyByte: IntArray, index: IntArray): IntArray {
        val keys = generateKeys(keyByte)
        val ipByte = initPermute(dataByte)
        val ipLeft = IntArray(32)
        val ipRight = IntArray(32)
        val tempLeft = IntArray(32)
        for (k in 0..31) {
            ipLeft[k] = ipByte[k]
            ipRight[k] = ipByte[32 + k]
        }
        for (i in index) {
            for (j in 0..31) {
                tempLeft[j] = ipLeft[j]
                ipLeft[j] = ipRight[j]
            }
            val key = IntArray(48)
            System.arraycopy(keys[i], 0, key, 0, 48)
            val tempRight = xor(pPermute(sBoxPermute(xor(expandPermute(ipRight), key))), tempLeft)
            System.arraycopy(tempRight, 0, ipRight, 0, 32)
        }
        val finalData = IntArray(64)
        for (i in 0..31) {
            finalData[i] = ipRight[i]
            finalData[32 + i] = ipLeft[i]
        }
        return finallyPermute(finalData)
    }

    private fun initPermute(originalData: IntArray): IntArray {
        val ipByte = IntArray(64)
        var i = 0
        var m = 1
        var n = 0
        var j: Int
        var k: Int
        while (i < 4) {
            j = 7
            k = 0
            while (j >= 0) {
                ipByte[i * 8 + k] = originalData[j * 8 + m]
                ipByte[i * 8 + k + 32] = originalData[j * 8 + n]
                j--
                k++
            }
            i++
            m += 2
            n += 2
        }
        return ipByte
    }

    private fun expandPermute(rightData: IntArray): IntArray {
        val epByte = IntArray(48)
        for (i in 0..7) {
            if (i == 0) {
                epByte[i * 6] = rightData[31]
            } else {
                epByte[i * 6] = rightData[i * 4 - 1]
            }
            epByte[i * 6 + 1] = rightData[i * 4]
            epByte[i * 6 + 2] = rightData[i * 4 + 1]
            epByte[i * 6 + 3] = rightData[i * 4 + 2]
            epByte[i * 6 + 4] = rightData[i * 4 + 3]
            if (i == 7) {
                epByte[i * 6 + 5] = rightData[0]
            } else {
                epByte[i * 6 + 5] = rightData[i * 4 + 4]
            }
        }
        return epByte
    }

    private fun xor(byteOne: IntArray, byteTwo: IntArray): IntArray {
        val xorByte = IntArray(byteOne.size)
        for (i in byteOne.indices) {
            xorByte[i] = byteOne[i] xor byteTwo[i]
        }
        return xorByte
    }

    private fun sBoxPermute(expandByte: IntArray): IntArray {
        val sBoxByte = IntArray(32)
        var binary = ""
        val s1 = arrayOf(intArrayOf(14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7), intArrayOf(0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8), intArrayOf(4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0), intArrayOf(15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13))
        val s2 = arrayOf(intArrayOf(15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10), intArrayOf(3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5), intArrayOf(0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15), intArrayOf(13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9))
        val s3 = arrayOf(intArrayOf(10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8), intArrayOf(13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1), intArrayOf(13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7), intArrayOf(1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12))
        val s4 = arrayOf(intArrayOf(7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15), intArrayOf(13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9), intArrayOf(10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4), intArrayOf(3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14))
        val s5 = arrayOf(intArrayOf(2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9), intArrayOf(14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6), intArrayOf(4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14), intArrayOf(11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3))
        val s6 = arrayOf(intArrayOf(12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11), intArrayOf(10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8), intArrayOf(9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6), intArrayOf(4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13))
        val s7 = arrayOf(intArrayOf(4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1), intArrayOf(13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6), intArrayOf(1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2), intArrayOf(6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12))
        val s8 = arrayOf(intArrayOf(13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7), intArrayOf(1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2), intArrayOf(7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8), intArrayOf(2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11))
        for (m in 0..7) {
            val i = expandByte[m * 6] * 2 + expandByte[m * 6 + 5]
            val j = expandByte[m * 6 + 1] * 2 * 2 * 2 + expandByte[m * 6 + 2] * 2 * 2 + expandByte[m * 6 + 3] * 2 + expandByte[m * 6 + 4]
            when (m) {
                0 -> binary = getBoxBinary(s1[i][j])
                1 -> binary = getBoxBinary(s2[i][j])
                2 -> binary = getBoxBinary(s3[i][j])
                3 -> binary = getBoxBinary(s4[i][j])
                4 -> binary = getBoxBinary(s5[i][j])
                5 -> binary = getBoxBinary(s6[i][j])
                6 -> binary = getBoxBinary(s7[i][j])
                7 -> binary = getBoxBinary(s8[i][j])
            }
            sBoxByte[m * 4] = Integer.parseInt(binary.substring(0, 1))
            sBoxByte[m * 4 + 1] = Integer.parseInt(binary.substring(1, 2))
            sBoxByte[m * 4 + 2] = Integer.parseInt(binary.substring(2, 3))
            sBoxByte[m * 4 + 3] = Integer.parseInt(binary.substring(3, 4))
        }
        return sBoxByte
    }

    private fun pPermute(e: IntArray): IntArray {
        return intArrayOf(//e = sBoxByte
                e[15], e[6], e[19], e[20], e[28], e[11], e[27], e[16], e[0], e[14], e[22], e[25], e[4], e[17], e[30], e[9], e[1], e[7], e[23], e[13], e[31], e[26], e[2], e[8], e[18], e[12], e[29], e[5], e[21], e[10], e[3], e[24])
    }

    private fun finallyPermute(e: IntArray): IntArray {
        return intArrayOf(//e = endByte
                e[39], e[7], e[47], e[15], e[55], e[23], e[63], e[31], e[38], e[6], e[46], e[14], e[54], e[22], e[62], e[30], e[37], e[5], e[45], e[13], e[53], e[21], e[61], e[29], e[36], e[4], e[44], e[12], e[52], e[20], e[60], e[28], e[35], e[3], e[43], e[11], e[51], e[19], e[59], e[27], e[34], e[2], e[42], e[10], e[50], e[18], e[58], e[26], e[33], e[1], e[41], e[9], e[49], e[17], e[57], e[25], e[32], e[0], e[40], e[8], e[48], e[16], e[56], e[24])
    }

    //generate 16 keys for xor
    private fun generateKeys(keyByte: IntArray): Array<IntArray> {
        val e = IntArray(56)
        val keys = Array(16) { IntArray(48) }
        val loop = intArrayOf(1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1)
        for (i in 0..6) {
            var j = 0
            var k = 7
            while (j < 8) {
                e[i * 8 + j] = keyByte[8 * k + i]
                j++
                k--
            }
        }
        for (i in 0..15) {
            var tempLeft: Int
            var tempRight: Int
            for (j in 0 until loop[i]) {
                tempLeft = e[0]
                tempRight = e[28]
                for (k in 0..26) {
                    e[k] = e[k + 1]
                    e[28 + k] = e[29 + k]
                }
                e[27] = tempLeft
                e[55] = tempRight
            }
            val t = intArrayOf(e[13], e[16], e[10], e[23], e[0], e[4], e[2], e[27], e[14], e[5], e[20], e[9], e[22], e[18], e[11], e[3], e[25], e[7], e[15], e[6], e[26], e[19], e[12], e[1], e[40], e[51], e[30], e[36], e[46], e[54], e[29], e[39], e[50], e[44], e[32], e[47], e[43], e[48], e[38], e[55], e[33], e[52], e[45], e[41], e[49], e[35], e[28], e[31])
            System.arraycopy(t, 0, keys[i], 0, 48)
        }
        return keys
    }


    /**
     * 将字符串转化为base64编码
     *
     * @param str 需要加密的String
     */
    fun encodeBase64(str: String): String {
        return Base64.getEncoder().encodeToString(str.toByteArray())
    }

    /**
     * 将base64编码的字符串进行解码
     *
     * @param str base64编码的字符串
     */
    fun decodeBase64(str: String): String {
        val bytes = Base64.getDecoder().decode(str)
        return String(bytes)
    }

    /**
     * 得到字符串的MD5值
     *
     * @param OrigString 需要转换的字符串
     * @return MD5值
     */
    private fun encodeMD5(OrigString: String): String {
        val md5Digest = DigestUtils.getMd5Digest()
        md5Digest.update(OrigString.toByteArray())
        return byte2hex(md5Digest.digest()).toLowerCase()
    }

    private fun byte2hex(byteString: ByteArray): String {
        val result = StringBuilder()
        var tmpChar: String
        for (aByteString in byteString) {
            tmpChar = Integer.toHexString(aByteString.toInt().and(255))
            if (tmpChar.length == 1)
                result.append("0").append(tmpChar)
            else
                result.append(tmpChar)
        }
        return result.toString().toUpperCase()
    }

    fun getEncParamStr(params: String, timeStamp: String, desKey: String): String {
        var param = params
        val token = getTripleMD5(param, timeStamp)
        param = encodeBase64(encodeDes(param, desKey))
        param = "params=$param&token=$token&timestamp=$timeStamp"
        return param
    }

    fun getEncParam(params: String, timeStamp: String, desKey: String): Parameters {
        val paramStr = getEncParamStr(params, timeStamp, desKey)
        val paramList = paramStr.split("&")
        return listOf(
                "params" to paramList[0].substring(7),
                "token" to paramList[1].substring(6),
                "timestamp" to paramList[2].substring(10)
        )
    }

    fun getTripleMD5(str1: String, str2: String): String {
        return encodeMD5(encodeMD5(str1) + encodeMD5(str2))
    }
}


